package com.ikingtech.platform.service.system.dict.controller;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ikingtech.framework.sdk.base.model.BatchParam;
import com.ikingtech.framework.sdk.base.model.DragOrderParam;
import com.ikingtech.framework.sdk.base.model.PageResult;
import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.context.security.Me;
import com.ikingtech.framework.sdk.core.response.R;
import com.ikingtech.framework.sdk.data.mybatisplus.helper.DragHelper;
import com.ikingtech.framework.sdk.data.mybatisplus.helper.DragHelperBuilder;
import com.ikingtech.framework.sdk.dict.api.DictItemApi;
import com.ikingtech.framework.sdk.dict.model.DictItemDTO;
import com.ikingtech.framework.sdk.dict.model.DictItemQueryParamDTO;
import com.ikingtech.framework.sdk.enums.common.DragTargetPositionEnum;
import com.ikingtech.framework.sdk.enums.system.dictionary.DictTypeEnum;
import com.ikingtech.framework.sdk.log.embedded.annotation.OperationLog;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.framework.sdk.web.annotation.ApiController;
import com.ikingtech.platform.service.system.dict.entity.DictDO;
import com.ikingtech.platform.service.system.dict.entity.DictItemDO;
import com.ikingtech.platform.service.system.dict.exception.DictExceptionInfo;
import com.ikingtech.platform.service.system.dict.service.DictItemRepository;
import com.ikingtech.platform.service.system.dict.service.DictRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;

/**
 * 系统管理-字典项管理
 * <p>1. 字典项需按租户隔离
 * <p>2. 字典项支持父子级关系
 *
 * @author tie yan
 */
@RequiredArgsConstructor
@ApiController(value = "/system/dict/item", name = "系统管理-字典项管理", description = "系统管理-字典项管理")
public class DictItemController implements DictItemApi {

    private final DictItemRepository repo;

    private final DictRepository dictRepo;

    /**
     * 添加字典项
     *
     * @param item 字典项DTO对象
     * @return 添加结果
     */
    @Override
    @OperationLog(value = "新增字典项", dataId = "#_res.getData()")
    @Transactional(rollbackFor = Exception.class)
    public R<String> add(DictItemDTO item) {
        DictDO dictEntity = this.dictRepo.getById(item.getDictId());
        if (DictTypeEnum.SYSTEM.name().equals(dictEntity.getType())) {
            throw new FrameworkException(DictExceptionInfo.ADD_SYSTEM_DICT_ITEM_IS_NOT_ALLOWED);
        }

        DictItemDO entity = Tools.Bean.copy(item, DictItemDO.class);
        entity.setId(Tools.Id.uuid());
        entity.setTenantCode(Me.tenantCode());
        entity.setDictCode(dictEntity.getCode());
        entity.setFullPath(this.repo.parseFullPath(item.getParentId(), entity.getId()));
        entity.setSortOrder(this.getMaxSortOrder(item.getParentId()) + 1);
        this.repo.save(entity);
        return R.ok(entity.getId());
    }

    /**
     * 删除字典项
     *
     * @param id 字典项ID
     * @return 删除结果
     */
    @Override
    @OperationLog(value = "删除字典项")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> delete(String id) {
        // 根据ID查询字典项实体
        DictItemDO entity = this.repo.getById(id);
        if (null == entity) {
            return R.ok();
        }
        // 查询字典类型
        String dictType = this.dictRepo.getObj(Wrappers.<DictDO>lambdaQuery()
                .select(DictDO::getType)
                .eq(DictDO::getId, id), String.class::cast);
        if (DictTypeEnum.SYSTEM.name().equals(dictType)) {
            throw new FrameworkException(DictExceptionInfo.DELETE_SYSTEM_DICT_ITEM_IS_NOT_ALLOWED);
        }
        // 根据全路径删除字典项
        this.repo.remove(Wrappers.<DictItemDO>query().lambda().likeRight(DictItemDO::getFullPath, entity.getFullPath()).eq(DictItemDO::getTenantCode, Me.tenantCode()));
        return R.ok();
    }

    /**
     * 更新字典项
     *
     * @param item 字典项DTO对象
     * @return 更新结果
     */
    @Override
    @OperationLog(value = "更新字典项")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> update(DictItemDTO item) {
        // 根据ID查询字典项实体
        DictItemDO entity = this.repo.getById(item.getId());
        if (null == entity) {
            throw new FrameworkException(DictExceptionInfo.DICT_ITEM_NOT_FOUND);
        }
        // 复制字典项DTO对象为新的实体对象
        DictItemDO newEntity = Tools.Bean.copy(item, DictItemDO.class);
        // 设置新的实体对象的完整路径
        newEntity.setFullPath(this.repo.parseFullPath(item.getParentId(), item.getId()));
        // 更新字典项实体
        this.repo.updateById(newEntity);
        // 如果新的实体对象的预设值与字典项DTO对象的预设值不相等，则更新预设值
        if (!newEntity.getPreset().equals(item.getPreset())) {
            this.repo.update(Wrappers.<DictItemDO>lambdaUpdate()
                    .set(DictItemDO::getPreset, newEntity.getPreset())
                    .likeRight(DictItemDO::getFullPath, newEntity.getFullPath())
                    .eq(DictItemDO::getTenantCode, Me.tenantCode()));
        }
        return R.ok();
    }

    /**
     * 分页查询字典项列表
     *
     * @param queryParam 查询参数
     * @return 分页结果
     */
    @Override
    public R<List<DictItemDTO>> page(DictItemQueryParamDTO queryParam) {
        return R.ok(PageResult.build(this.repo.page(new Page<>(queryParam.getPage(), queryParam.getRows()), DictItemRepository.createWrapper(queryParam, Me.tenantCode()))).convert(entity -> Tools.Bean.copy(entity, DictItemDTO.class)));
    }

    /**
     * 获取所有字典项数据
     *
     * @return 返回所有字典项数据的DTO对象列表
     */
    @Override
    public R<List<DictItemDTO>> all() {
        return R.ok(Tools.Coll.convertList(this.repo.list(Wrappers.<DictItemDO>lambdaQuery()
                .eq(DictItemDO::getTenantCode, Me.tenantCode())
                .orderByDesc(DictItemDO::getCreateTime)), entity -> Tools.Bean.copy(entity, DictItemDTO.class)));
    }

    /**
     * 获取字典项详情
     *
     * @param id 字典项ID
     * @return 字典项详情
     */
    @Override
    public R<DictItemDTO> detail(String id) {
        // 通过ID获取字典项实体
        DictItemDO entity = this.repo.getById(id);
        // 如果实体为空，则抛出异常
        if (null == entity) {
            throw new FrameworkException(DictExceptionInfo.DICT_ITEM_NOT_FOUND);
        }
        // 将实体转换为字典项DTO并返回
        return R.ok(Tools.Bean.copy(entity, DictItemDTO.class));
    }

    /**
     * 根据字典ID查询字典项列表
     *
     * @param dictId 字典ID
     * @return 查询结果
     */
    @Override
    public R<List<DictItemDTO>> listByDictId(String dictId) {
        return R.ok(Tools.Coll.convertList(this.repo.list(Wrappers.<DictItemDO>lambdaQuery().eq(DictItemDO::getDictId, dictId)), entity -> Tools.Bean.copy(entity, DictItemDTO.class)));
    }

    /**
     * 根据字典编码批量查询字典项列表
     *
     * @param dictCodes 字典编码列表
     * @return 查询结果
     */
    @Override
    public R<List<DictItemDTO>> listByDictCodes(BatchParam<String> dictCodes) {
        // 如果字典编码列表为空，则返回空列表
        if (Tools.Coll.isBlank(dictCodes.getList())) {
            return R.ok(new ArrayList<>());
        }
        // 查询字典项列表，并转换为字典项DTO列表
        return R.ok(Tools.Coll.convertList(this.repo.list(Wrappers.<DictItemDO>lambdaQuery()
                .in(DictItemDO::getDictCode, dictCodes.getList())
                .eq(DictItemDO::getTenantCode, Me.tenantCode())), entity -> Tools.Bean.copy(entity, DictItemDTO.class)));
    }

    /**
     * 拖拽操作
     *
     * @param dragParam 拖拽参数
     * @return 操作结果
     */
    @Override
    public R<Object> drag(DragOrderParam dragParam) {
        if (DragTargetPositionEnum.NONE.equals(dragParam.getPosition())) {
            return R.ok();
        }
        // 创建拖拽帮助器
        DragHelper<DictItemDO> dragHelper = DragHelperBuilder.builder(
                        // 获取当前拖拽项的父级ID和排序号
                        () -> this.gt(dragParam.getParentId(), dragParam.getCurrentOrder()),
                        // 获取目标拖拽项的父级ID和排序号
                        () -> this.gt(dragParam.getTargetParentId(), dragParam.getTargetOrder()),
                        // 获取当前拖拽项的父级ID和目标拖拽项的父级ID之间的排序号
                        (starOrder, endOrder) -> this.between(dragParam.getParentId(), starOrder, endOrder)
                )
                // 设置拖拽帮助器的拖拽位置
                .which(dragParam.getParentId(), dragParam.getTargetParentId(), DragTargetPositionEnum.INNER.equals(dragParam.getPosition()))
                // 获取当前拖拽项
                .currentNode(() -> this.repo.getById(dragParam.getCurrentId()))
                // 获取目标拖拽项
                .targetNode(() -> this.repo.getById(dragParam.getTargetId()))
                // 获取目标拖拽项的最大排序号
                .maxSortOrder(() -> this.getMaxSortOrder(dragParam.getTargetId()))
                // 设置目标拖拽项的位置
                .beforeTarget(DragTargetPositionEnum.BEFORE.equals(dragParam.getPosition()))
                .build();
        // 执行拖拽操作
        this.repo.updateBatchById(dragHelper.drag());
        return R.ok();
    }

    /**
     * 获取最大排序号
     *
     * @param parentId 父ID
     * @return 最大排序号
     */
    private Integer getMaxSortOrder(String parentId) {
        // 查询排序号列表
        List<Number> orders = this.repo.listObjs(Wrappers.<DictItemDO>query().lambda()
                .select(DictItemDO::getSortOrder)
                .eq(DictItemDO::getParentId, parentId)
                .eq(DictItemDO::getTenantCode, Me.tenantCode())
                .orderByDesc(DictItemDO::getSortOrder));
        // 如果排序号列表为空，则返回0，否则返回第一个排序号的整数值
        return Tools.Coll.isBlank(orders) ? 0 : orders.get(0).intValue();
    }


    /**
     * 根据父节点ID、排序值范围和租户代码查询字典项列表
     *
     * @param parentNodeId 父节点ID
     * @param startValue   排序值起始值
     * @param endValue     排序值结束值
     * @return 符合条件的字典项列表
     */
    private List<DictItemDO> between(Object parentNodeId, Object startValue, Object endValue) {
        return this.repo.list(Wrappers.<DictItemDO>query().lambda()
                // 根据排序值范围查询
                .between(DictItemDO::getSortOrder, startValue, endValue)
                // 根据父节点ID查询
                .eq(DictItemDO::getParentId, parentNodeId)
                // 根据租户代码查询
                .eq(DictItemDO::getTenantCode, Me.tenantCode())
        );
    }

    /**
     * 根据父节点ID和排序号获取大于指定排序号的字典项列表
     *
     * @param parentNodeId 父节点ID
     * @param value 排序号
     * @return 字典项列表
     */
    private List<DictItemDO> gt(Object parentNodeId, Object value) {
        return this.repo.list(Wrappers.<DictItemDO>query().lambda()
                // 父节点ID等于指定值
                .eq(DictItemDO::getParentId, parentNodeId)
                // 排序号大于指定值
                .gt(DictItemDO::getSortOrder, value)
                // 租户代码等于当前租户代码
                .eq(DictItemDO::getTenantCode, Me.tenantCode())
        );
    }

}
